// *************************************************************************************************
//
// MK3D Engine
// --------------------------------------
// Copyright (C) 2007-2011 Zeb (mailto: zebbey@gmail.com)
//
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// *************************************************************************************************

#include "mk_header.h"
#include "mk_camera.h"
#include "mk_render_window.h"
#include "mk_engine.h"
#include "mk_math.h"

namespace MK3DE
{
	CCamera::CCamera() : 
		_position(0, 0, -100), 
		_lookDirection(0, 0, 1), 
		_upDirection(0, 1, 0), 
		_aspectRatio(4.0f / 3.0f), 
		_fov(MK_QUAR_PI),
		_width(0), 
		_height(0),
		_nearDis(1.0f), 
		_farDis(10000.0f),
		_bPerspectiveProjection(TRUE),
		_viewDirty(TRUE),
		_projDirty(TRUE)
	{
		Update(0);
	}

	CCamera::~CCamera()
	{

	}

	void CCamera::SetAspectRatio(float aspect)
	{
		_aspectRatio = aspect;
		_projDirty = TRUE;
	}

	void CCamera::SetFOV(float fov)
	{
		_fov = fov;
		_projDirty = TRUE;
	}

	void CCamera::SetWidth(float w)
	{
		_width = w;
		_projDirty = TRUE;
	}

	void CCamera::SetHeight(float h)
	{
		_height = h;
		_projDirty = TRUE;
	}

	void CCamera::SetViewNear(float n)
	{
		_nearDis = n;
		_projDirty = TRUE;
	}

	void CCamera::SetViewFar(float f)
	{
		_farDis = f;
		_projDirty = TRUE;
	}

	void CCamera::SetPosition(D3DXVECTOR3& pos)
	{
		_position = pos;
		_viewDirty = TRUE;
	}

	void CCamera::SetLookDirection(D3DXVECTOR3& dir)
	{
		_lookDirection = dir;
		D3DXVec3Normalize(&_lookDirection, &_lookDirection);
		_viewDirty = TRUE;
	}

	void CCamera::SetUpDirection(D3DXVECTOR3& up)
	{
		_upDirection = up;
		D3DXVec3Normalize(&_upDirection, &_upDirection);
		_viewDirty = TRUE;
	}

	void CCamera::MoveViewZ(float dis)
	{
		_position += _lookDirection * dis;
		_viewDirty = TRUE;
	}

	void CCamera::MoveViewX(float dis)
	{
		_position.x -= _matView._11 * dis;
		_position.y -= _matView._21 * dis;
		_position.z -= _matView._31 * dis;
		_viewDirty = TRUE;
	}

	void CCamera::MoveAxisY(float dis)
	{
		_position.y += dis;
		_viewDirty = TRUE;
	}

	void CCamera::MoveAxisX(float dis)
	{
		_position.x += dis;
		_viewDirty = TRUE;
	}

	void CCamera::MoveAxisZ(float dis)
	{
		_position.z += dis;
		_viewDirty = TRUE;
	}

	void CCamera::RotationViewX(FLOAT rad)
	{
		D3DXMATRIX mat;
		D3DXMatrixRotationAxis(&mat, &GetRightDirection(), -rad);
		D3DXVec3TransformNormal(&_lookDirection, &_lookDirection, &mat);
		_viewDirty = TRUE;
	}

	void CCamera::RotationAxisY(FLOAT rad)
	{
		D3DXMATRIX mat;
		D3DXMatrixRotationAxis(&mat, &D3DXVECTOR3(0, 1, 0), -rad);
		D3DXVec3TransformNormal(&_lookDirection, &_lookDirection, &mat);
		D3DXVec3TransformNormal(&_upDirection, &_upDirection, &mat);
		_viewDirty = TRUE;
	}

	void CCamera::EnablePerspectiveProjection(BOOL bEnable)
	{
		_bPerspectiveProjection = bEnable;
		_projDirty = TRUE;
	}

	void CCamera::Update(DWORD dt)
	{
		if (_viewDirty)
			__RecalculateViewMatrix();

		if (_projDirty)
			__RecalculateProjMatrix();

		if (_viewDirty || _projDirty)
		{
			_matViewProj = _matView * _matProj;
			ExtractViewFrustum(_matViewProj);

			_viewDirty = FALSE;
			_projDirty = FALSE;
		}
	}

	void CCamera::__RecalculateViewMatrix()
	{
		D3DXVECTOR3 lookAt = _position + _lookDirection;
		D3DXMatrixLookAtLH(&_matView, &_position, &lookAt, &_upDirection);
	}

	void CCamera::__RecalculateProjMatrix()
	{
		if (_bPerspectiveProjection)
			D3DXMatrixPerspectiveFovLH(&_matProj, _fov, _aspectRatio, _nearDis, _farDis);
		else
			D3DXMatrixOrthoLH(&_matProj, _width, _height, _nearDis, _farDis);
	}

	void CCamera::GetPickRay(D3DXVECTOR3& origPos, D3DXVECTOR3& dir, D3DXVECTOR2& pos2D, DWORD width, DWORD height)
	{
		D3DXVECTOR3 v((2.0f * pos2D.x / (float)width - 1.0f) / _matProj._11,
			-(2.0f * pos2D.y / (float)height - 1.0f) / _matProj._22,
			1.0f);

		D3DXMATRIX invMatView;
		D3DXMatrixInverse(&invMatView, NULL, &_matView);

		dir.x  = v.x * invMatView._11 + v.y * invMatView._21 + v.z * invMatView._31;
		dir.y  = v.x * invMatView._12 + v.y * invMatView._22 + v.z * invMatView._32;
		dir.z  = v.x * invMatView._13 + v.y * invMatView._23 + v.z * invMatView._33;
		origPos.x = invMatView._41 ;
		origPos.y = invMatView._42 ; 
		origPos.z = invMatView._43 ;
	}

	void CCamera::ProjectPoint(/* out */ D3DXVECTOR2& outPos, D3DXVECTOR3& point, DWORD width, DWORD height)
	{
		D3DXVECTOR3 op;
		MK3DE::ProjectPoint(op, point, width, height, _matView, _matProj);
		outPos.x = op.x;
		outPos.y = op.y;
	}
}